{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {
    "colab_type": "text",
    "id": "view-in-github"
   },
   "source": [
    "<a href=\"https://colab.research.google.com/github/peremartra/Large-Language-Model-Notebooks-Course/blob/main/3-LangChain/HF_gpt_j_6b_Moderation_System.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gC-aV7J4K_uR"
   },
   "source": [
    "<div align=\"center\">\n",
    "<h1><a href=\"https://github.com/peremartra/Large-Language-Model-Notebooks-Course\">LLM Hands On Course</a></h1>\n",
    "    <h3>Understand And Apply Large Language Models</h3>\n",
    "    <h2>Create a Moderation system with LangChain and Hugging Face.</h2>\n",
    "    <p>by <b>Pere Martra</b></p>\n",
    "</div>\n",
    "\n",
    "<br>\n",
    "\n",
    "<div align=\"center\">\n",
    "    &nbsp;\n",
    "    <a target=\"_blank\" href=\"https://www.linkedin.com/in/pere-martra/\"><img src=\"https://img.shields.io/badge/style--5eba00.svg?label=LinkedIn&logo=linkedin&style=social\"></a>\n",
    "    \n",
    "</div>\n",
    "\n",
    "<br>\n",
    "<hr>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "gIV3BBY_LULN"
   },
   "source": [
    "# How To Create a Moderation System Using LangChain & Hugging Face.\n",
    "\n",
    "This notebook continues the One created with models from OpenAI:\n",
    "https://github.com/peremartra/Large-Language-Model-Notebooks-Course/blob/main/3-LangChain/LangChain_OpenAI_Moderation_System.ipynb\n",
    "\n",
    "\n",
    "We are going to create a Moderation System based in a Model from Hugging Face.\n",
    "\n",
    "First the Model reads the User comments and answer them.\n",
    "\n",
    "Then the same Model receives the answer and identify any kind of negativity modifiyng if necessary the comment.\n",
    "\n",
    "The intention is to  prevent a text entry by the user from influencing a negative or out-of-tone response from the comment system.\n",
    "\n",
    "\n",
    "Of course the notebook using the OpenAI Models has performed much better than this one.\n",
    "\n",
    "With the OpenAI API you can use much more powerful models than what we can load on our local machine or in Google Colab.\n",
    "\n",
    "The Notebook has been tested with many models, I have finally left two. One of them can be run in the free version of Colab, while the larger one should be run using the PRO version of Colab.\n",
    "You can download the notebook and run it on your machine, you will need a GPU with 16 MB of memory."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "0NT5vH9tyYWT"
   },
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "oI5hcVHoK-Ol",
    "outputId": "69888e86-3a5b-43a7-ab93-b68f96a16cb1"
   },
   "outputs": [],
   "source": [
    "#Install de LangChain and openai libraries.\n",
    "%pip install langchain\n",
    "%pip install openai\n",
    "%pip install transformers"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1R1QSNGpOYCK"
   },
   "source": [
    "## Importing LangChain Libraries.\n",
    "* PrompTemplate: provides functionality to create prompts with parameters.\n",
    "* OpenAI:  To interact with the OpenAI models.\n",
    "* LLMChain: To create chains, where the prompts or the results can pass from one step to another inside the chain."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "CuBxdjshLYDM"
   },
   "outputs": [],
   "source": [
    "from langchain import PromptTemplate\n",
    "from langchain.chains import LLMChain\n",
    "\n",
    "from langchain.llms import HuggingFacePipeline\n",
    "from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline, AutoModelForSeq2SeqLM"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "AIfbM8wcOh1h"
   },
   "outputs": [],
   "source": [
    "import torch\n",
    "import os\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "D6UOQjyJP9rb"
   },
   "outputs": [],
   "source": [
    "#model_id = \"EleutherAI/gpt-neo-2.7B\" #free version\n",
    "model_id = \"EleutherAI/gpt-j-6b\" #Colab PRO\n",
    "\n",
    "tokenizer = AutoTokenizer.from_pretrained(model_id)\n",
    "model = AutoModelForCausalLM.from_pretrained(model_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "ONwHLj5Lwfde"
   },
   "outputs": [],
   "source": [
    "pipe = pipeline(\n",
    "    \"text-generation\",\n",
    "    model=model,\n",
    "    tokenizer=tokenizer,\n",
    "    max_new_tokens=128,\n",
    "    eos_token_id = int(tokenizer.convert_tokens_to_ids('.')),\n",
    "    device_map='auto'\n",
    ")\n",
    "assistant_llm = HuggingFacePipeline(pipeline=pipe)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "UluKkDpVTb8W"
   },
   "source": [
    "Create the template for the first model called assistant.\n",
    "\n",
    "The prompt receives 2 variables, the sentiment and the customer_request, or customer comment. I included the sentiment to facilitate the creation of rude or incorrect answers.\n",
    "\n",
    "Note the **eos_token_id**, I incorporated it to help the Model stop generating text. It has a tendency to put much more text than necessary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "kN5ArR3DQum0"
   },
   "outputs": [],
   "source": [
    "# Instruction how the LLM must respond the comments,\n",
    "assistant_template = \"\"\"\n",
    "You are {sentiment} social media post commenter, you will respond to the following post\n",
    "Post: \"{customer_request}\"\n",
    "Comment:\n",
    "\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "A5kulgS3Tg-6"
   },
   "outputs": [],
   "source": [
    "#Create the prompt template to use in the Chain for the first Model.\n",
    "assistant_prompt_template = PromptTemplate(\n",
    "    input_variables=[\"sentiment\", \"customer_request\"],\n",
    "    template=assistant_template\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "kqscrfkaTlze"
   },
   "source": [
    "Now we create a First Chain. Just chaining the assistant_prompt_template and the model. The model will receive the prompt generated with the prompt_template."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "t-TojsLETi1I"
   },
   "outputs": [],
   "source": [
    "assistant_chain = LLMChain(\n",
    "    llm=assistant_llm,\n",
    "    prompt=assistant_prompt_template,\n",
    "    output_key=\"assistant_response\",\n",
    "    verbose=False\n",
    ")\n",
    "#the output of the formatted prompt will pass directly to the LLM."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "dBIymqlCTq4l"
   },
   "source": [
    "To execute the chain created it's necessary to call the .run method of the chain, and pass the variables necessaries.\n",
    "\n",
    "In our case: customer_request and sentiment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "ZWdmIxBGTo-S"
   },
   "outputs": [],
   "source": [
    "#Support function to obtain a response to a user comment.\n",
    "def create_dialog(customer_request, sentiment):\n",
    "    #callint the .run method from the chain created Above.\n",
    "    assistant_response = assistant_chain.run(\n",
    "        {\"customer_request\": customer_request,\n",
    "        \"sentiment\": sentiment}\n",
    "    )\n",
    "    return assistant_response"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "FOlZymNDTuk1"
   },
   "source": [
    "## Obtain answers from our first Model Unmoderated.\n",
    "\n",
    "The customer post is really rude, we are looking for a rude answer from our Model, and to obtain it we are changing the sentiment."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "ZJMH8CEuTtUA"
   },
   "outputs": [],
   "source": [
    "# This the customer request, or customere comment in the forum moderated by the agent.\n",
    "# feel free to update it.\n",
    "customer_request = \"\"\"Your product is a piece of shit. I want my money back!\"\"\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "pJneR3CsTzhB",
    "outputId": "b93f845c-38fe-4974-b484-abba9ae6e0fd"
   },
   "outputs": [],
   "source": [
    "# Our assistatnt working in 'nice' mode.\n",
    "assistant_response=create_dialog(customer_request, \"nice\")\n",
    "print(f\"assistant response: {assistant_response}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "KznRBMbeT2K2",
    "outputId": "7acf5d05-3d3e-4cfd-fed6-1cc057cbc3ef"
   },
   "outputs": [],
   "source": [
    "#Our assistant running in rude mode.\n",
    "assistant_response = create_dialog(customer_request, \"rude\")\n",
    "print(f\"assistant response: {assistant_response}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "sVPa-bl3MSbT"
   },
   "source": [
    "As you can see the answers we obtain is really far from being polite and we can't publish this messages to the forum, especially if they come from our company's AI assistant."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "O9nzBDprNytc"
   },
   "source": [
    "## Moderator\n",
    "Let's create the second moderator. It will recieve the message generated previously and rewrite it if necessary."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "KebcVF5BVWdF"
   },
   "outputs": [],
   "source": [
    "#The moderator prompt template\n",
    "moderator_template = \"\"\"\n",
    "You are the moderator of an online forum, you are strict and will not tolerate any negative comments.\n",
    "You will look at this next comment and, if it is negative, you will transform it to positive. Avoid any negative words.\n",
    "If it is nice, you will let it remain as is and repeat it word for word.\n",
    "###\n",
    "Original comment: {comment_to_moderate}\n",
    "###\n",
    "Edited comment:\"\"\"\n",
    "\n",
    "# We use the PromptTemplate class to create an instance of our template that will use the prompt from above and store variables we will need to input when we make the prompt.\n",
    "moderator_prompt_template = PromptTemplate(\n",
    "    input_variables=[\"comment_to_moderate\"],\n",
    "    template=moderator_template\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "KDh0Ik3zN6um"
   },
   "outputs": [],
   "source": [
    "moderator_llm = assistant_llm"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "BLUUW-xxOAli"
   },
   "outputs": [],
   "source": [
    "#We build the chain for the moderator.\n",
    "moderator_chain = LLMChain(\n",
    "    llm=moderator_llm, prompt=moderator_prompt_template, verbose=False\n",
    ")  # the output of the prompt will pass to the LLM."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "id": "3qZRGTrIT7KE",
    "outputId": "73e2f529-89f6-44ab-b936-77fef6e014e2"
   },
   "outputs": [],
   "source": [
    "# To run our chain we use the .run() command\n",
    "moderator_says = moderator_chain.run({\"comment_to_moderate\": assistant_response})\n",
    "\n",
    "print(f\"moderator_says: {moderator_says}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Azfj6yzXV_ae"
   },
   "source": [
    "Maybe the message is not perfect, but for sure that is more polite than the one produced by the ***rude assistant***."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "1GtiMkHuWEhH"
   },
   "source": [
    "## LangChain System\n",
    "Now is Time to put both models in the same Chain and that they act as if they were a sigle model.\n",
    "\n",
    "We have both models, amb prompt templates, we only need to create a new chain and see hot it works.\n",
    "\n",
    "First we create two chain, one for each pair of prompt and model."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "Md19hXy_VaKP"
   },
   "outputs": [],
   "source": [
    "#The optput of the first chain must coincide with one of the parameters of the second chain.\n",
    "#The parameter is defined in the prompt_template.\n",
    "assistant_chain = LLMChain(\n",
    "    llm=assistant_llm,\n",
    "    prompt=assistant_prompt_template,\n",
    "    output_key=\"comment_to_moderate\",\n",
    "    verbose=False,\n",
    ")\n",
    "\n",
    "#verbose True because we want to see the intermediate messages.\n",
    "moderator_chain = LLMChain(\n",
    "    llm=moderator_llm,\n",
    "    prompt=moderator_prompt_template,\n",
    "    verbose=True\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "Daen3DEWWNEq"
   },
   "source": [
    "**SequentialChain** is used to link different chains and parameters.\n",
    "\n",
    "It's necessary to indicate the chains and the parameters that we shoud pass in the **.run** method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "dXcC6OsJWJSE"
   },
   "outputs": [],
   "source": [
    "from langchain.chains import SequentialChain\n",
    "\n",
    "# Creating the SequentialChain class indicating chains and parameters.\n",
    "assistant_moderated_chain = SequentialChain(\n",
    "    chains=[assistant_chain, moderator_chain],\n",
    "    input_variables=[\"sentiment\", \"customer_request\"],\n",
    "    verbose=True,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "g-Xhe1nLWSTY"
   },
   "source": [
    "Lets use our Moderating System!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/",
     "height": 466
    },
    "id": "dsFnDEv8WQVG",
    "outputId": "af002461-c4f0-4cd2-82f4-38535609a110"
   },
   "outputs": [],
   "source": [
    "# We can now run the chain.\n",
    "assistant_moderated_chain.run({\"sentiment\": \"rude\", \"customer_request\": customer_request})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "id": "6RQIYARhVdvv"
   },
   "source": [
    "Sometimes the Moderator works fine, sometimes not. The Model is not able to detect correctly the sentiment, and if it detects the sentimet the comment modified can be worse than the origina or have no sense at all.\n",
    "\n",
    "The model used is far to be a state of art Model, is really far from what we can obtain with model offered by OpenAI, or OpenSource models like LLAMA.\n",
    "\n",
    "If you want to check how this same solutions works with OpenAI API check this notebook: https://github.com/peremartra/Large-Language-Model-Notebooks-Course/blob/main/3-LangChain/LangChain_OpenAI_Moderation_System.ipynb"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "id": "vSORmbneV5tj"
   },
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "colab": {
   "include_colab_link": true,
   "machine_shape": "hm",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 0
}
